﻿using System.Collections.Generic;
using System.IO;
using FunctionalCLR.Core;
using FunctionalCLR.Core.Logging;
using Zove.Compiler;
using Zove.Compiler.EquationTransformer;

namespace Zove.Runtime
{
    /// <summary>
    /// 
    /// </summary>
    public class DefaultRuntimeModelLoader : IRuntimeModelLoader
    {
        private string className = string.Empty;

        private List<string> previousLoadedClasses = new List<string>();

        /// <summary>
        /// 
        /// </summary>
        public IClassLoader ClassLoader
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        public DefaultRuntimeModelLoader()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        public DefaultRuntimeModelLoader(string className)
            : this()
        {
            this.className = className;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public RuntimeModel LoadByClassName(string className)
        {
            this.Reset();
            return this.LoadByClassName("", className);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public RuntimeModel LoadByUrl(string url)
        {
            this.Reset();
            string source = File.ReadAllText(url);
            return this.LoadBySource(source);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public RuntimeModel LoadBySource(string source)
        {
            this.Reset();
            ZoomClass c = new ZoomClass();
            c = ZoveDependencyManager.Instance.ClassLoader.LoadFromSource(source);
            return this.LoadByClass(c);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public RuntimeModel LoadByClass(ZoomClass c)
        {
            this.Reset();
            return this.LoadByClass("", c);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="className"></param>
        /// <returns></returns>
        protected RuntimeModel LoadByClassName(string prefix, string className)
        {
            ZoomClass zvc = this.ClassLoader.Load(className);
            RuntimeModel rm = this.InternalLoad(prefix, zvc);

            return rm;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="zc"></param>
        /// <returns></returns>
        protected RuntimeModel InternalLoad(string prefix, ZoomClass zc)
        {
            Utils.Debugging("loading class {0}", zc.Name);
            if (this.previousLoadedClasses.Contains(zc.Name))
            {
                throw new RecursiveDefinedClassException(zc.Name);
            }
            this.previousLoadedClasses.Add(zc.Name);

            RuntimeModel rm = LoadByClass(prefix, zc);

            this.previousLoadedClasses.Remove(zc.Name);

            return rm;
        }

        /// <summary>
        /// mat is a dyn type loads
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="className"></param>
        /// <returns></returns>
        protected RuntimeModel LoadByClass(
            string prefix, ZoomClass zc)
        {
            //    LoggingMessage msg = new LoggingMessage();
            //    msg.Description = string.Format(
            //        "The class {0} is defined in its own defintion.",
            //        zc.Name);
            //    msg.FileName = zc.Name;
            //    msg.LineNumber = 1;

            //    Logger.LogError(msg);

            //    return null;
            //}
            
            RuntimeModel rm = new RuntimeModel();
            rm.Name = zc.Name;

            if (zc.BaseClasses.Count > 0)
            {
                foreach (string bc in zc.BaseClasses)
                {
                    RuntimeModel parent
                        = this.LoadByClassName(prefix, bc);
                    rm.Add(parent);
                }
            }

            foreach (ZoomVariable var in zc.Variables)
            {
                string p =
                    string.IsNullOrEmpty(prefix)
                    ? var.Name : prefix + "." + var.Name;
                if (var.Type == "factor")
                {
                    RuntimeFactor rf = rm.AddFactor(p);
                    rf.Unit = var.Unit;
                }
                else
                {
                    RuntimeModel m = this.LoadByClassName(p, var.Type);
                    rm.Add(m);
                }
            }

            foreach (ZoomEquation equ in zc.Equations)
            {
                string equation = Utils.Prefixize(
                    prefix, equ.Equation,
                    ZoveDependencyManager.Instance.FunctionManager);
                equation = PreprocessEquation(zc, rm, equation);
                RuntimeEquation re = new RuntimeEquation(equation);
                rm.Equations.Add(re);

                DefaultEquationTransformer et
                    = new DefaultEquationTransformer(re.Equation, rm);
                re.Left = et.LeftTree;
                re.Right = et.RightTree;

                foreach (ZoomElement e in equ.Elements)
                {
                    if (ZoveDependencyManager.Instance.ConstantManager.IsConstant(e.Name))
                    {
                        continue;
                    }

                    string m = string.IsNullOrEmpty(prefix)
                        ? e.Name : prefix + "." + e.Name;
                    RuntimeFactor rf = rm.GetFactorByName(m);
                    if (rf == null)
                    {
                        //throw new FactorNotDeclaredException(
                        //    e.Name, equ.Equation, zvc.Name);
                        LoggingMessage msg = new LoggingMessage();
                        msg.Description = string.Format(
                            "The variable {0} used in equation {1} is not declared",
                            e.Name, equ.Equation, zc.Name);
                        msg.FileName = zc.Name;
                        msg.LineNumber = equ.LineNumber;

                        DefaultDependencyManager.Instance.Logger.WriteLog(msg.Description);
                        continue;
                    }
                    re.Factors.Add(rf);
                    rf.Equations.Add(re);

                    RuntimeRule rr = new RuntimeRule();
                    rr.Factor = rf;
                    rr.Equation = re;
                    if (et.Nodes.ContainsKey(rf.Name))
                    {
                        rr.Root = et.Nodes[rf.Name];
                    }
                    else
                    {
                        rr.Root = null;
                    }
                    rf.Rules.Add(rr);

                    string rrId = RuntimeModel.GetRuleId(re, rf);
                    rm.Rules[rrId] = rr;
                }
            }

            return rm;
        }

        /// <summary>
        /// Replace the function calls in the expression 
        /// with the function bodies
        /// </summary>
        /// <param name="zc">the class model</param>
        /// <param name="rm">the runtime model</param>
        /// <param name="equation">the equation</param>
        /// <returns>the resulted expression</returns>
        public static string PreprocessEquation(
            ZoomClass zc, RuntimeModel rm, string equation)
        {
            UserDefinedFunctionConverter converter
                = new UserDefinedFunctionConverter(zc.Name);

            string[] sides = equation.Split('=');
            string lhs = PreprocessExpr(zc, rm, converter, sides[0]);
            string rhs = PreprocessExpr(zc, rm, converter, sides[1]);
            equation = lhs + "=" + rhs;

            return equation;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="zc"></param>
        /// <param name="rm"></param>
        /// <param name="converter"></param>
        /// <param name="expr"></param>
        /// <returns></returns>
        public static string PreprocessExpr(
            ZoomClass zc, RuntimeModel rm,
            UserDefinedFunctionConverter converter, string expr)
        {
            // if the function is declared in its class
            // place the class name in the front of the function call
            // to identify it
            expr = Utils.PrefixizeFunction(zc.Name, expr,
                ZoveDependencyManager.Instance.FunctionManager);
            Parser lp
                = ZoveDependencyManager.Instance.ParserFactory.CreateParser(
                expr, rm);
            Node lrx = lp.Parse();
            string lhs = converter.Convert(lrx);

            return lhs;
        }

        /// <summary>
        /// Cleans the class loading footprints
        /// </summary>
        public void Reset()
        {
            this.previousLoadedClasses.Clear();
        }
    }
}
